package client_package;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.LinkedList;
import java.util.Scanner;
import java.util.TreeMap;

import javax.sound.sampled.AudioFormat;
import javax.swing.JOptionPane;

/**
 * Classe représentant le moteur de l'application
 */
public class Controleur {
	private LinkedList<Vue> vues;
	
	
	private Socket socket;
	private Socket socket_son;
	private Socket socket_confirm;
	private Socket socket_sound;
	
	private BufferedReader in;
	private PrintWriter out;
	private BufferedReader in_son;
	private PrintWriter out_son;
	private BufferedReader in_confirm;
	private PrintWriter out_confirm;
	private InputStream in_sound;
	private OutputStream out_sound;
	
	private Thread t; // ThreadListener
	
	private Thread tInfos; // ThreadListenerInfos
	private Thread tAttenteConnection;
	private Thread tListenerSound;
	private Thread tSendRecord;
	private Thread tRecevoirLaMain;

	private String login;
	
	private String nomFichLogin;
	
	private TreeMap<String,String> langues;

	private Connection cn=null;
	
	public enum StatutSound {Enregistrement, Ecoute, Pas_En_Communication};
	public enum Statut {Hors_Ligne, Disponible, En_Communication};
	
	private StatutSound statutSound;	
	
	/**
	 * Constructeur du controleur
	 * @param host : Adresse IP du serveur
	 */
	public Controleur(String host) {
		String urlBD = "jdbc:mysql://mysql1.alwaysdata.com/mestest_pt";
		String loginBD = "mestest_user_pt";
		String passwordBD = "pt2a";
		setLangues(new TreeMap<String,String>());
		// Etape 1 : Chargement du driver
		try {
			Class.forName("com.mysql.jdbc.Driver");
		
			// Etape 2 : récupération de la connexion

			cn = DriverManager.getConnection(urlBD, loginBD, passwordBD);
			
			restaure();
		} catch (SQLException e1) {
			e1.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		
		nomFichLogin = "login.sauv";
		
		setStatutSound(StatutSound.Pas_En_Communication);
		setVues(new LinkedList<Vue>());
		
		try {
			socket = new Socket(InetAddress.getByName(host), 2009);
			socket_son = new Socket(InetAddress.getByName(host), 2010);
			socket_confirm = new Socket(InetAddress.getByName(host), 2011);
			socket_sound = new Socket(InetAddress.getByName(host), 2012);
			socket_sound.setTcpNoDelay(true);
			socket_sound.setPerformancePreferences(0, 2, 1);
			
			in = new BufferedReader (new InputStreamReader (socket.getInputStream()));
			out = new PrintWriter(socket.getOutputStream());
			
			in_son = new BufferedReader (new InputStreamReader (socket_son.getInputStream()));
			out_son = new PrintWriter(socket_son.getOutputStream());
			
			in_confirm = new BufferedReader (new InputStreamReader (socket_confirm.getInputStream()));
			out_confirm = new PrintWriter(socket_confirm.getOutputStream());
			
			in_sound = socket_sound.getInputStream();
			out_sound = socket_sound.getOutputStream();
			
			setLogin(null);
			menu();
		} catch (UnknownHostException e) {
			JOptionPane.showMessageDialog(null, "Le serveur est introuvable");
		} catch (IOException e) {
			JOptionPane.showMessageDialog(null, "Le serveur est indisponible pour le moment");
		}
	}
	
	public AudioFormat getAudioFormat() {
	    float sampleRate = 24000.0F;
	    int sampleSizeBits = 16;
	    int channels = 1;
	    boolean signed = true;
	    boolean bigEndian = true;
	    //AudioFormat.Encoding.ULAW
	    return new AudioFormat(sampleRate, sampleSizeBits, channels, signed, bigEndian);
	}
	
	/**
	 * Getter pour le statut du son lors de la conversation
	 * @return le statut du son
	 */
	public StatutSound getStatutSound() {
		return statutSound;
	}
	/**
	 * Setter pour le statut du son lors de la conversation
	 * @param statutSound : le statut du son
	 */
	public void setStatutSound(StatutSound statutSound) {
		this.statutSound = statutSound;
	}
	
	/**
	 * Setter du login
	 * @param log : login de la personne connecté
	 */
	public void setLogin(String log) {
		login = log;
	}

	/**
	 * Getter du login
	 * @return le login de la personne connecté
	 */
	public String getLogin() {
		return login;
	}
	
	/**
	 * Getter de l'ensemble des langues
	 * @return Une collection des langues disponibles
	 */
	public TreeMap<String,String> getLangues() {
		return langues;
	}
	
	/**
	 * Setter de l'ensemble des langues
	 * @param langues : une collection des langues disponibles
	 */
	private void setLangues(TreeMap<String,String> langues) {
		this.langues = langues;
	}

	/**
	 * Getter du Thread tInfos
	 * @return Un Thread
	 */
	public Thread getTInfos() {
		return tInfos;
	}
	
	/**
	 * Reset du Thread tInfos
	 */
	public void setTInfos() {
		tInfos = new Thread(new ThreadListenerInfos(this));
	}
	
	/**
	 * Getter du Thread tListener
	 * @return Un Thread
	 */
	public Thread getTListener() {
		return t;
	}
	
	/**
	 * Getter de connection
	 * @return un type connection
	 */
	public Connection getCn() {
		return cn;
	}

	/**
	 * Reset du Thread tListenerInfos
	 */
	public void setTListener() {
		t = new Thread(new ThreadListener(this));
	}

	/**
	 * Getter du Thread tListenerSound
	 * @return Un Thread
	 */
	public Thread getTListenerSound() {
		return tListenerSound;
	}

	/**
	 * Reset du Thread tListenerInfos
	 */
	public void setTListenerSound() {
		tListenerSound = new Thread(new ThreadListenerSound(this));
	}
	
	/**
	 * Getter du Thread tListenerSound
	 * @return Un Thread
	 */
	public Thread getTSendRecord() {
		return tSendRecord;
	}

	/**
	 * Reset du Thread tListenerInfos
	 */
	public void setTSendRecord() {
		tSendRecord = new Thread(new ThreadSendRecord(this));
	}

	/**
	 * Getter du Thread tRecevoirLaMain
	 * @return Un Thread
	 */
	public Thread getTRecevoirLaMain() {
		return tRecevoirLaMain;
	}

	/**
	 * Reset du Thread tRecevoirLaMain
	 */
	public void setTRecevoirLaMain() {
		this.tRecevoirLaMain = new Thread(new ThreadRecevoirLaMain(this, getIn_Confirm()));
	}
	
	/**
	 * Getter de l'ensemble des vues
	 * @return Une collection des vues
	 */
	private LinkedList<Vue> getVues() {
		return vues;
	}

	/**
	 * Setter de l'ensemble des vues
	 * @param vues : Une cosetLanguesParleesllection des vues
	 */
	private void setVues(LinkedList<Vue> vues) {
		this.vues = vues;
	}
	
	/**
	 * Getter de la dernière vue
	 * @return La dernière vue
	 */
	public Vue getVue() {
		return getVues().getLast();
	}

	/**
	 * Getter de la vue du menu
	 * @return La vue du menu
	 */
	public Vue getVueMenu() {
		return getVues().getFirst();
	}

	/**
	 * Setter de la dernière vue
	 * @param v : La nouvelle vue
	 */
	public void setVues(Vue v) {
		getVues().addLast(v);
	}
	
	/**
	 * Getter du buffer d'écriture
	 * @return Un objet de type PrintWriter
	 */
	public PrintWriter getOut() {
		return out;
	}
	
	/**
	 * Getter du buffer d'écriture
	 * @return Un objet de type PrintWriter
	 */
	public PrintWriter getOut_Son() {
		return out_son;
	}

	/**
	 * Getter du buffer d'écriture
	 * @return Un objet de 	public type PrintWriter
	 */
	public PrintWriter getOut_Confirm() {
		return out_confirm;
	}

	/**
	 * Getter du buffer d'écriture du son
	 * @return Un objet de type OutputStream
	 */
	public OutputStream getOut_Sound() {
		return out_sound;
	}
	
	/**
	 * Getter du buffer de lecture
	 * @return Un objet de type BufferReader
	 */
	public BufferedReader getIn() {
		return in;
	}

	/**
	 * Getter du buffer de lecture
	 * @return Un objet de type BufferReader
	 */
	public BufferedReader getIn_son() {
		return in_son;
	}

	/**
	 * Getter du buffer de lecture
	 * @return Un objet de type BufferReader
	 */
	public BufferedReader getIn_Confirm() {
		return in_confirm;
	}

	/**
	 * Getter du buffer de lecture du son
	 * @return Un objet de type InputStream
	 */
	public InputStream getIn_sound() {
		return in_sound;
	}

	/**
	 * Méthode permettant de supprimer le dernière vue
	 */
	private void removeVue() {
		vues.removeLast();
	}

	/**
	 * Méthode permettant de fermer la vue
	 * @param vue : la vue à fermer
	 */
	public void fermerVue(Vue vue) {
		if (vue instanceof VueMenu ) {
			// Quitte l'aplication
			vue.dispose();
			this.removeVue();
			quitter();
		} else if (vue instanceof VueConversation ) {
			vue.dispose();
			this.removeVue();
			this.getVue().setVisible(true);
			setStatutSound(StatutSound.Pas_En_Communication);

			byte[] b = new byte[1024];
			b[0]= 0;
			try {
				getOut_Sound().write(b, 0, 1024);
			} catch (IOException e) {
				e.printStackTrace();
			}
			tInfos = new Thread(new ThreadListenerInfos(this));
			tInfos.start();
			tAttenteConnection = new Thread(new ThreadAttenteConnection(this));
			tAttenteConnection.start();
		} else if (vue instanceof VueConnexion) {
			vue.dispose();
			this.removeVue();
		} else if (vue instanceof VueModificationInfo) {
			vue.dispose();
			this.removeVue();
			this.getVue().setVisible(true);
			tInfos = new Thread(new ThreadListenerInfos(this));
			tInfos.start();
			tAttenteConnection = new Thread(new ThreadAttenteConnection(this));
			tAttenteConnection.start();
		} else {
			vue.dispose();
			this.removeVue();
			this.getVue().setVisible(true);
		}
		//System.out.println("Je sors de fermerVue !" + login);
	}
	
	/**
	 * Méthode permettant d'afficher le menu
	 */
	public void menu() {
		try {
			this.setVues(new VueMenu(this));
			this.getVueMenu().setVisible(true); 	
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Méthode permettant d'afficher la fenêtre de connexion
	 */
	public void vueConnexion() {
		File f = null;
		Scanner scanner=null;
		boolean retenirLogin = false;
		String login = null;
		try 
		{
			f = new File(nomFichLogin);
			scanner = new Scanner(f);
			login = scanner.nextLine();
			retenirLogin = true;
		} catch (FileNotFoundException e1) { }
		out.println("connexion");
		out.flush();
		getVue().setVisible(false);
		setVues(new VueConnexion(this, retenirLogin, login));
		getVue().setVisible(true);
	}

	/**
	 * Méthode permettant d'afficher la fenêtre d'inscription
	 */
	public void VueInscription() {
		out.println("enregistrement");
		out.flush();
		getVue().setVisible(false);
		setVues(new VueInscription(this));
		getVue().setVisible(true);
	}
	
	/**
	 * Méthode permettant d'afficher la fenêtre du salon
	 */
	public void VueSalon() {
		getVue().setVisible(false);
		setVues(new VueSalon(this));
		getVue().setVisible(true);
		setStatutSound(StatutSound.Pas_En_Communication);
		tAttenteConnection = new Thread(new ThreadAttenteConnection(this));
		tAttenteConnection.start();
		tInfos = new Thread(new ThreadListenerInfos(this));
		tInfos.start();
	}
	
	public void vueModifInfo() {
		out.println("modification");
		out.flush();
		tInfos.interrupt();
		getVue().setVisible(false);
		setVues(new VueModificationInfo(this));
		getVue().setVisible(true);
	}
	/**
	 * Méthode permettant de réaliser la connexion d'un personne
	 * @param login : Le login de la personne
	 * @param mdpCrypt : Le mot de passe de la personne
	 * @param b : Si on doit retenir le login ou pas
	 */
	public void connexion(String login, char[] mdpCrypt, boolean b) {
		if((login.length() != 0) && (mdpCrypt.length != 0)) {
			if(!login.equals("exit") && !login.equals("%Client%")) {
				out.println(login);
				out.flush();
				out.println(String.valueOf(mdpCrypt));
				out.flush();
				String infoRetour = "";
				try {
					infoRetour = in.readLine();
					if(infoRetour.equals("OK")) {
						JOptionPane.showMessageDialog(null, "Vous avez été connecté");
						setLogin(login);
						if(b) { // Check box "retenir le login" coché
							FileWriter f = null;
							try 
							{	
								f = new FileWriter(nomFichLogin, false);
								f.write(login,0,login.length());
								f.write("\n");
								//System.out.println("fichier crée");
							} 
							catch(IOException ex) 
							{
								ex.printStackTrace();
							} 
							finally 
							{
								if(f != null) 
								{		
									try 
									{
										f.close();
									} catch (IOException e1) {
										//System.out.println("fichier non fermé");
										e1.printStackTrace();
									}
								}
							}
						} else {
							effacerFichierLogin();
						}
						fermerVue(getVue());
						VueSalon();
					} else {
						JOptionPane.showMessageDialog(null, infoRetour);
					}
				} catch (IOException e) {
					JOptionPane.showMessageDialog(null, "Problème de connexion");
				}
			} else {
				JOptionPane.showMessageDialog(null, "login incorrect");
			}
		} else {
			JOptionPane.showMessageDialog(null, "Veuillez remplir tous les champs");
		}
	}
	
	
	public void annuler(Vue vue) {
		if(vue instanceof VueConnexion) {
			fermerVue(vue);
			getVue().setVisible(true);
		}
	}
	
	/**
	 * Méthode permettant de démarrer une communication
	 * @param loginOppose : login de l'autre personne
	 * @param statut : statut de l'autre personne
	 */
	public void communiquer(String loginOppose, Statut statut) {
		String confirm="";
		if(loginOppose != null) {
			if(statut.equals(Statut.Disponible)) {
				
				out_son.println(loginOppose);
				out_son.flush();
				
				try {
					confirm = in_confirm.readLine();
					
					if (confirm.equals("OK"))
					{
						out.println("com");
						out.flush();
						try {
							if(login != null) {
								out.println(loginOppose);
								out.flush();
								
								tInfos.interrupt();
								String info = in.readLine();
								if(info != null) {
									if(info.equals("Com : OK")) {
										
										getVue().setVisible(false);
										setVues(new VueConversation(this, loginOppose));
										getVue().setVisible(true);
										((VueConversation)getVue()).setPasserLaMain(true);
										setStatutSound(StatutSound.Enregistrement);
										t = new Thread(new ThreadListener(this));
									    t.start();
									    tRecevoirLaMain = new Thread(new ThreadRecevoirLaMain(this, getIn_Confirm()));
									    tRecevoirLaMain.start();
										tSendRecord = new Thread(new ThreadSendRecord(this));
										tSendRecord.start();
									} else if (info.equals("Com : user not accept")) {
										JOptionPane.showMessageDialog(null, "Votre correspondant a refusé la communication");
									} else if (info.equals("Com : user not connected")) {
										JOptionPane.showMessageDialog(null, "Le correspondant que vous tentez de joindre n'est pas connecté");
									} else if (info.equals("Com : user in comm")) {
										JOptionPane.showMessageDialog(null, "Le correspondant que vous tentez de joindre est déjà en communication");
									} else if (info.equals("Com : user not exists")) {
										JOptionPane.showMessageDialog(null, "Le correspondant que vous tentez de joindre est inexistant");
									} else {
										JOptionPane.showMessageDialog(null, "Il y a eu un problème lors de la connexion !");
										fermerVue(getVue());
									}
								} else {
									JOptionPane.showMessageDialog(null, "Il y a eu un problème lors de la connexion !");
									tInfos = new Thread(new ThreadListenerInfos(this));
									tInfos.start();
									fermerVue(getVue());
									}
							} else {
								JOptionPane.showMessageDialog(null, "Tu n'as rien à faire la !!");
							}
						} catch (IOException e) {
							JOptionPane.showMessageDialog(null, "Il y a eu un problème lors de la connexion !");
							fermerVue(getVue());
						}
					} else {
						JOptionPane.showMessageDialog(null, loginOppose + " a refusé la conversation");
					}
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			} else {
				JOptionPane.showMessageDialog(null, loginOppose + " n'est pas disponible");
			}
		}else{
			JOptionPane.showMessageDialog(null, "Vous devez séléctionner un correspondant");
		}
	}
	
	/**
	 * Méthode permettant de déconnecter l'utilisateur
	 * Méthode appelé dans VueConversation et VueSalon
	 */
	public void deconnexion() {
		
		if(getVue() instanceof VueConversation) {
			out_confirm.println("fini");
			out_confirm.flush();
			out_confirm.println("quitter");
			out_confirm.flush();
			out.println("%Client% : exit");
			out.flush();
			t.interrupt();
			fermerVue(getVue());
		} else if(getVue() instanceof VueSalon) {
			tInfos.interrupt();
			out.println("deconnexion");
			out.flush();
			out_son.println("exit");
			out_son.flush();
			fermerVue(getVue());
		} else {
			//System.out.println("HEIN ?");
			fermerVue(getVue());
		}
		//System.out.println("Je sors de la deconnexion !" + login);
		//System.out.println(getStatutSound());
	}
	
	/**
	 * Méthode permettant d'envoyer un message texte
	 * @param texte : le message texte à envoyer
	 */
	public void envoyertexte(String texte){
		String ch = login + " : " + texte;
		((VueConversation)getVue()).alimente(ch);
		out.println(ch);
		out.flush();
	}

	/**
	 * Méthode permettant d'inscrire une personne
	 * @param login : Le login
	 * @param mdpCrypt : Le mot de passe
	 * @param nom : Le nom
	 * @param prenom : Le prénom
	 * @param sexe : Le sexe (Homme / Femme)
	 * @param type : Le type (Interprete / Locuteur)
	 * @param LangueMat : La langue maternelle
	 * @param LanguesParlees : L'ensemble des langues parlées (dans le cas d'un interprete)
	 */
	public void inscrire(String login, char[] mdpCrypt, String nom, String prenom,String sexe,String type, String LangueMat, TreeMap<String,String> LanguesParlees ){
		if (login.length() == 0 || String.valueOf(mdpCrypt).length() == 0 || nom.length() == 0 || prenom.length() == 0 || sexe.length() == 0 ){
			JOptionPane.showMessageDialog(null, "Tous les champs sont obligatoires");
		}
		else{
			if(!login.equals("exit") && !login.equals("%Client%")) {
				out.println(login);
				out.flush();
				out.println(String.valueOf(mdpCrypt));
				out.flush();
				out.println(nom);
				out.flush();
				out.println(prenom);
				out.flush();
				out.println(sexe);
				out.flush();
				out.println(type);
				out.flush();
				out.println(LangueMat);
				out.flush();
				if (type.equals("Interprete"))
				{
					out.println(String.valueOf(LanguesParlees.size()));
					out.flush();
					for(String l : LanguesParlees.values()){
						out.println(l);
						out.flush();
					}
				}
				String reponse = "";
				try {
					reponse = in.readLine();
				} catch (IOException e) {
					e.printStackTrace();
				}
				
				if (reponse.equals("user exists")) {
					JOptionPane.showMessageDialog(null, "Le login est déjà utilisé");
				}
				else if (reponse.equals("user create")){
					JOptionPane.showMessageDialog(null, "L'inscription est confirmée");
					fermerVue(getVue());
				}
				else{
					JOptionPane.showMessageDialog(null, "WHAT THE PHOQUE???");
					
				}
			} else {
				JOptionPane.showMessageDialog(null, "Ce login est interdit");
			}
		}
	}
	
	/**
	 * Méthode permettant de modifier une personne
	 * @param login : Le login
	 * @param mdpCrypt : Le mot de passe
	 * @param nom : Le nom
	 * @param prenom : Le prénom
	 * @param sexe : Le sexe (Homme / Femme)
	 * @param type : Le type (Interprete / Locuteur)
	 * @param LangueMat : La langue maternelle
	 * @param LanguesParlees : L'ensemble des langues parlées (dans le cas d'un interprete)
	 */
	public void modifier(String login, char[] mdpCrypt, String nom, String prenom,String sexe,String type, String LangueMat, TreeMap<String,String> LanguesParlees ){
		if (login.length() == 0 || String.valueOf(mdpCrypt).length() == 0 || nom.length() == 0 || prenom.length() == 0 || sexe.length() == 0 ){
			JOptionPane.showMessageDialog(null, "Tous les champs sont obligatoires");
		}
		else{
			out.println("modif");
			out.flush();
			out.println(String.valueOf(mdpCrypt));
			out.flush();
			out.println(type);
			out.flush();
			if (type.equals("Interprete")){
				
				out.println(String.valueOf(LanguesParlees.size()));
				out.flush();
				for(String l : LanguesParlees.values()){
					out.println(l);
					out.flush();
				}
			}
			String reponse = "";
			try {
				reponse = in.readLine();
			} catch (IOException e) {
				e.printStackTrace();
			}
			
			if (reponse.equals("user modif"))
				JOptionPane.showMessageDialog(null, "Le compte a été modifié");
			else
				JOptionPane.showMessageDialog(null, "Erreur dans la modification");
			
			fermerVue(getVue());
		}		
	}
	
	/**
	 * Méthode permettant de restaurer les langues à partir de la BDD
	 */
	private void restaure()
	{
		Statement st=null; 
		ResultSet rs =null;
		try {
			st = cn.createStatement();
			
			String sql = "SELECT * FROM Langues ORDER BY nomLangue";

			rs = st.executeQuery(sql);
			while (rs.next()) {
				String langue = rs.getString("nomLangue");
				langues.put(langue,langue);
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Méthode gérant la fermeture de l'application en fermant tous les buffers et tous les sockets
	 */
	public void quitter() {
		try {
			out.println("quitter");
			out.flush();
			
			out_son.println("exit");
			out_son.flush();
			
			in.close();
			out.close();
			
			in_son.close();
			out_son.close();
			
			in_sound.close();
			out_sound.close();
			
			in_confirm.close();
			out_confirm.close();
			
			socket.close();
			socket_son.close();
			socket_sound.close();
			socket_confirm.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		System.exit(0);
	}

	/**
	 * Méthode permettant de passer la main lors d'une conversation
	 */
	public void passerLaMain() {
		out_confirm.println("fini");
		out_confirm.flush();
		
		// pour ne pas quitter
		out_confirm.println("fini");
		out_confirm.flush();
		((VueConversation)getVue()).setPasserLaMain(false);
		setStatutSound(StatutSound.Ecoute);
		setTListenerSound();
		getTListenerSound().start();
	}

	
	/**
	 * Méthode permettant d'éffacer le fichier de sauvegarde du login
	 */
	public void effacerFichierLogin() {
		File f2 = new File(nomFichLogin);
		f2.delete();
	}


}


